/**
 * 
 */
package gemini.basic.dao.impl;

import gemini.basic.dao.GmnDao;
import gemini.basic.util.ExceptionNotificationHelper;
import gemini.basic.util.Reject;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.time.DateUtils;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

/**
 * Collections of utility methods that help dealing with DAO
 *
 */
public class GmnDaoImpl extends HibernateDaoSupport implements GmnDao {
    
    /** {@inheritDoc} */
    public Criterion equalIgnoreTime(String propertyName, Date queryDate) {
        Date dateNoTime = DateUtils.truncate(queryDate, Calendar.DATE);
        Date nextDayNoTime = DateUtils.addDays(dateNoTime, 1);
        
        // dateNoTime <= propertyName < nextDayNoTime
        return Restrictions.and(Restrictions.ge(propertyName, dateNoTime), 
                Restrictions.lt(propertyName, nextDayNoTime));
    }
    
    /** {@inheritDoc} */
    public Criterion equalsOrLessThanIgnoreTime(String propertyName, Date queryDate) {
        Date dateNoTime = DateUtils.truncate(queryDate, Calendar.DATE);
        Date nextDayNoTime = DateUtils.addDays(dateNoTime, 1);

        // < (dd.mm.yyyy 00:00:00 + 1d)
        return Restrictions.lt(propertyName, nextDayNoTime);
    }
    
    /** {@inheritDoc} */
    public Criterion equalsOrGreaterThanIgnoreTime(String propertyName, Date queryDate) {
        Date dateNoTime = DateUtils.truncate(queryDate, Calendar.DATE);

        // >= dd.mm.yyyy 00:00:00
        return Restrictions.ge(propertyName, dateNoTime);
    }
    
    /** {@inheritDoc} */
    @Transactional(propagation = Propagation.REQUIRED)
    public Object saveOrUpdate(Object entity)  throws DataAccessException,
        DataIntegrityViolationException, OptimisticLockingFailureException {
        String objectName = entity.getClass().getSimpleName();
        Reject.ifNull(entity);
        Reject.ifEmpty(objectName, 
                "The name of the persistent object type must not be empty.");
        
        try {
            getHibernateTemplate().saveOrUpdate(entity);
        } catch (HibernateOptimisticLockingFailureException e) {
            String message = "The current " + objectName
                + " was modified or deleted in the meantime.";
            ExceptionNotificationHelper.notifyOptimisticLockingFailure(
                    message, objectName, e);
        }
        
        return entity;
    }
    
    /** {@inheritDoc} */
    @Transactional(propagation = Propagation.REQUIRED)
    public Object saveOrUpdateAndFlush(Object entity)  throws DataAccessException,
        DataIntegrityViolationException, OptimisticLockingFailureException {
        Object tmp = saveOrUpdate(entity);
        getHibernateTemplate().flush();
        return tmp;
    }
    
    /** {@inheritDoc} */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public int findCountByCriteria(final DetachedCriteria hibernateCriteria)
        throws DataAccessException {
        Assert.notNull(hibernateCriteria, "DetachedCriteria must not be null");
        Object result =  getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Criteria executableCriteria = hibernateCriteria.getExecutableCriteria(session);
                executableCriteria.setProjection(Projections.rowCount());
                
                return executableCriteria.uniqueResult();
            }
        });
        if (result == null) {
            result = 0;
        }
        
        return (Integer) result;
    }

    /** {@inheritDoc} */
    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public List<Object> findByCriteria(DetachedCriteria hibernateCriteria)
            throws DataAccessException {
        @SuppressWarnings("unchecked")
		List<Object> result = getHibernateTemplate().findByCriteria(hibernateCriteria);
        return result;
    }

    /** {@inheritDoc} */
    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public List<Object> findByQueryString(String queryString, 
            final Object... values) throws DataAccessException {
        @SuppressWarnings("unchecked")
		List<Object> result = getHibernateTemplate().find(queryString, values);
        return result;
    }

    
    
}
